package com.dycx.user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dycx.common.RestResponse;
import com.dycx.common.context.UserInfo;
import com.dycx.common.context.UserInfoContext;
import com.dycx.common.vendor.weixin.WxUtils;
import com.dycx.common.vendor.weixin.vo.CodeOauth2VO;
import com.dycx.common.vendor.weixin.vo.CodeSessionVO;
import com.dycx.message.dto.VerificationCodeDTO;
import com.dycx.message.service.VerificationCodeService;
import com.dycx.system.service.ConfigService;
import com.dycx.system.vo.SystemConfig;
import com.dycx.user.constants.UserConstants;
import com.dycx.user.dto.UserDTO;
import com.dycx.user.entity.FansDO;
import com.dycx.user.entity.UserDO;
import com.dycx.user.entity.UserInfoDO;
import com.dycx.user.service.FansService;
import com.dycx.user.service.UserAuthService;
import com.dycx.user.service.UserInfoService;
import com.dycx.user.service.UserService;
import com.dycx.user.vo.UserVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.beans.Transient;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Slf4j
@Service()
@AllArgsConstructor
public class UserAuthImpl implements UserAuthService {

    private UserService userService;
    private VerificationCodeService verificationCodeService;
    private ConfigService configService;
    private FansService fansService;
    private UserInfoService userInfoService;

    /**
     * 保存登录信息
     *
     * @param uid
     * @return
     */
    @Override
    public String creatJWT(Integer uid) {
        UserVO userVO = userService.load(uid);

        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(userVO.getUid());
        userInfo.setUsername(userVO.getNickname());

        //创建token
        String token = JWT.create()
                .addPayloads(BeanUtil.toBean(userInfo, Map.class))
                .setExpiresAt(DateUtil.offsetSecond(DateUtil.date(), UserConstants.LOGIN_TIME)) //指定令牌过期时间
                .sign(JWTSignerUtil.hs256(UserConstants.JWT_KEY.getBytes()));//签名

        //登录日志传输操作人信息
        UserInfoContext.set(userInfo);

        return token;
    }

    /**
     * 账号密码登录
     *
     * @param userDto
     * @return
     */
    @Override
    public String login(UserDTO userDto) {
        String password = DigestUtils.md5Hex(userDto.getPassword() + UserConstants.PASSWORD_SALT);

        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getPassword, password)
                .and(wq -> wq.eq(UserDO::getUsername, userDto.getUsername())
                        .or()
                        .eq(UserDO::getMobile, userDto.getUsername())
                );

        UserDO userDO = userService.getOne(queryWrapper);
        Assert.notNull(userDO, "登录名或密码不正确");
        if (!userDO.getStatus()) {
            throw new RuntimeException("账户已被禁止登录");
        }
        return creatJWT(userDO.getUid());
    }

    /**
     * 验证码登录
     *
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public String smsLogin(UserDTO dto) {
        log.info("短信登录dto=========================================>{}", JSON.toJSONString(dto));
        VerificationCodeDTO verificationCodeDTO = new VerificationCodeDTO();
        verificationCodeDTO.setMobile(dto.getMobile());
        verificationCodeDTO.setCode(dto.getCode());
        verificationCodeService.checkVerificationCode(verificationCodeDTO);

        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getMobile, dto.getMobile());

        UserDO userDO = userService.getOne(queryWrapper);
        if (ObjectUtil.isNull(userDO)) { //如果不存在，则添加用户
            userDO = new UserDO();
            userDO.setShareUid(dto.getShareUid());
            userDO.setMobile(dto.getMobile());
            userDO.setStatus(true);
            if (!userService.save(userDO)) throw new RuntimeException("创建用户失败");

            UserInfoDO userInfoDO = new UserInfoDO();
            userInfoDO.setTryNum(UserConstants.TRY_NUM);
            userInfoDO.setUid(userDO.getUid());
            userInfoDO.setNickname("用户" + userDO.getMobile().substring(7, 11));
            if (!userInfoService.save(userInfoDO)) throw new RuntimeException("添加用户信息失败");
        }

        log.info("短信登录用户添加DO=========================================>{}", JSON.toJSONString(userDO));

        if (!userDO.getStatus()) {
            throw new RuntimeException("账户已被禁止登录");
        }


        /***********绑定openid/关联推广信息***********************************************************/
        if (StrUtil.isNotBlank(dto.getWxMpOpenId())) {
            //查询粉丝表是否有推广用户
            LambdaQueryWrapper<FansDO> fansWrapper = new LambdaQueryWrapper<>();
            fansWrapper.eq(FansDO::getOpenid, dto.getWxMpOpenId());
            FansDO fansDO = fansService.getOne(fansWrapper);
            if (ObjectUtil.isNotEmpty(fansDO) && ObjectUtil.isNotEmpty(fansDO.getSceneId())) {
                //推广用户
                userDO.setShareUid(fansDO.getSceneId());

                //赠送积分
                LambdaQueryWrapper<UserInfoDO> queryWrapperInfo = new LambdaQueryWrapper<>();
                queryWrapperInfo.eq(UserInfoDO::getUid, userDO.getUid());

                UserInfoDO userInfoDO = new UserInfoDO();
                userInfoDO.setCredit(fansDO.getCredit());

                userInfoService.update(userInfoDO, queryWrapperInfo);
            }

            userDO.setWxUnionId(dto.getWxUnionid());
            userDO.setWxMpOpenId(dto.getWxMpOpenId());
            userService.updateById(userDO);
        }

        if (StrUtil.isNotBlank(dto.getWxMiniOpenId())) {
            userDO.setWxUnionId(dto.getWxUnionid());
            userDO.setWxMiniOpenId(dto.getWxMiniOpenId());
            userService.updateById(userDO);
        }

        if (StrUtil.isNotBlank(dto.getWxAppOpenId())) {
            userDO.setWxUnionId(dto.getWxUnionid());
            userDO.setWxAppOpenId(dto.getWxAppOpenId());
            userService.updateById(userDO);
        }

        return creatJWT(userDO.getUid());
    }

    /**
     * 微信公众号登录
     *
     * @param code
     * @return
     */
    @Override
    public RestResponse wxMpLogin(String code) {
        Assert.notBlank(code, "code不能为空");

        SystemConfig configVO = configService.getSystemConfig();
        log.info("==============================>>>>>>>code:{}", code);
        CodeOauth2VO vo = WxUtils.codeOauth2(configVO.getWxMpAppId(), configVO.getWxMpAppSecret(), code);

        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getWxUnionId, vo.getUnionId());

        UserDO userDO = userService.getOne(queryWrapper);
        if (ObjectUtil.isEmpty(userDO)) {
            LambdaQueryWrapper<UserDO> queryWrapper2 = new LambdaQueryWrapper<>();
            queryWrapper2.eq(UserDO::getWxMpOpenId, vo.getOpenid());

            userDO = userService.getOne(queryWrapper2);
            if (ObjectUtil.isEmpty(userDO)) {
                Map<String, String> map = new HashMap<>();
                map.put("openid", vo.getOpenid());
                map.put("unionid", vo.getUnionId());
                return RestResponse.message(-3, "用户不存在，请绑定手机号", map);
            }
        }

        if (StrUtil.isBlank(userDO.getWxMpOpenId())) {
            userDO.setWxMpOpenId(vo.getOpenid());
            userService.updateById(userDO);
        }

        if (!userDO.getStatus()) {
            throw new RuntimeException("账户已被禁止登录");
        }

        return RestResponse.success(creatJWT(userDO.getUid()));
    }

    /**
     * 微信小程序登录
     *
     * @param code
     * @return
     */
    @Override
    public RestResponse wxMiniLogin(String code) {
        Assert.notBlank(code, "code不能为空");

        SystemConfig configVO = configService.getSystemConfig();
        CodeSessionVO vo = WxUtils.jsCode2Session(configVO.getWxMiniAppId(), configVO.getWxMiniAppSecret(), code);
        log.info("jsCode2Session:CodeSessionVO =========================>" + JSON.toJSONString(vo));

        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getWxUnionId, vo.getUnionId());

        UserDO userDO = userService.getOne(queryWrapper);
        if (ObjectUtil.isEmpty(userDO)) {
            LambdaQueryWrapper<UserDO> queryWrapper2 = new LambdaQueryWrapper<>();
            queryWrapper2.eq(UserDO::getWxMiniOpenId, vo.getOpenid());

            userDO = userService.getOne(queryWrapper2);
            if (ObjectUtil.isEmpty(userDO)) {
                Map<String, String> map = new HashMap<>();
                map.put("openid", vo.getOpenid());
                map.put("unionid", vo.getUnionId());
                return RestResponse.message(-3, "用户不存在，请绑定手机号", map);
            }
        }

        if (StrUtil.isBlank(userDO.getWxMiniOpenId())) {
            userDO.setWxMiniOpenId(vo.getOpenid());
            userService.updateById(userDO);
        }

        if (!userDO.getStatus()) {
            throw new RuntimeException("账户已被禁止登录");
        }

        return RestResponse.success(creatJWT(userDO.getUid()));
    }

    /**
     * 微信APP登录
     *
     * @param code
     * @return
     */
    @Override
    public RestResponse wxAppLogin(String code) {
        Assert.notBlank(code, "code不能为空");

        SystemConfig configVO = configService.getSystemConfig();
        log.info("==============================>>>>>>>code:{}", code);
        CodeOauth2VO vo = WxUtils.codeOauth2(configVO.getWxAppAppId(), configVO.getWxAppAppSecret(), code);

        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getWxUnionId, vo.getUnionId());

        UserDO userDO = userService.getOne(queryWrapper);
        if (ObjectUtil.isEmpty(userDO)) {
            LambdaQueryWrapper<UserDO> queryWrapper2 = new LambdaQueryWrapper<>();
            queryWrapper2.eq(UserDO::getWxAppOpenId, vo.getOpenid());

            userDO = userService.getOne(queryWrapper2);
            if (ObjectUtil.isEmpty(userDO)) {
                Map<String, String> map = new HashMap<>();
                map.put("openid", vo.getOpenid());
                map.put("unionid", vo.getUnionId());
                return RestResponse.message(-3, "用户不存在，请绑定手机号", map);
            }
        }

        if (StrUtil.isBlank(userDO.getWxAppOpenId())) {
            userDO.setWxAppOpenId(vo.getOpenid());
            userService.updateById(userDO);
        }

        if (!userDO.getStatus()) {
            throw new RuntimeException("账户已被禁止登录");
        }

        return RestResponse.success(creatJWT(userDO.getUid()));
    }

    /**
     * 注册
     *
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public Boolean register(UserDTO dto) {
        VerificationCodeDTO verificationCodeDTO = new VerificationCodeDTO();
        verificationCodeDTO.setMobile(dto.getMobile());
        verificationCodeDTO.setCode(dto.getCode());
        verificationCodeService.checkVerificationCode(verificationCodeDTO);

        LambdaQueryWrapper<UserDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserDO::getMobile, dto.getMobile());
        UserDO userDO = userService.getOne(queryWrapper);

        Assert.isNull(userDO, "用户已存在，请登录");

        userDO = new UserDO();
        userDO.setShareUid(dto.getShareUid());
        userDO.setMobile(dto.getMobile());
        userDO.setStatus(true);
        userDO.setPassword(DigestUtils.md5Hex(dto.getPassword() + UserConstants.PASSWORD_SALT));

        if (!userService.save(userDO)) throw new RuntimeException("注册失败");

        UserInfoDO userInfoDO = new UserInfoDO();
        userInfoDO.setTryNum(UserConstants.TRY_NUM);
        userInfoDO.setUid(userDO.getUid());
        userInfoDO.setNickname("用户" + userDO.getMobile().substring(7, 11));
        return userInfoService.save(userInfoDO);
    }

    /**
     * 验证是否有接口访问权限
     *
     * @param request
     * @return
     */
    @Override
    public RestResponse<String> auth(HttpServletRequest request) {
        String token = request.getHeader("authorization");
        String source = request.getHeader("source");
        try {
            JWTUtil.verify(token, UserConstants.JWT_KEY.getBytes());//验证令牌
            JWTValidator.of(token).validateDate(DateUtil.date()); //验证是否过期
        } catch (Exception e) {
            return RestResponse.notLogin("登录无效或过期");
        }

        //读取信息
        final JWT jwt = JWTUtil.parseToken(token);
        UserInfo userInfo = BeanUtil.toBean(jwt.getPayloads(), UserInfo.class);
        userInfo.setSource(Convert.toInt(source));

        //单进程存储登录信息，后面调用
        UserInfoContext.set(userInfo);

        //即将过期2小时内重新生成token
        String newToken = null;
        if ((LocalDateTimeUtil.toEpochMilli(LocalDateTime.now()) / 1000) > userInfo.getExp() - UserConstants.LOGIN_TIME / 2) {
            //创建token
            newToken = JWT.create()
                    .addPayloads(BeanUtil.toBean(userInfo, Map.class))
                    .setExpiresAt(DateUtil.offsetSecond(DateUtil.date(), UserConstants.LOGIN_TIME))
                    .sign(JWTSignerUtil.hs256(UserConstants.JWT_KEY.getBytes()));//签名
        }

        return RestResponse.success(newToken);
    }

    /**
     * 解析token信息
     *
     * @param token
     * @return
     */
    @Override
    public UserVO getTokenInfo(String token) {
        if (StrUtil.isBlank(token)) return null;

        try {
            JWTUtil.verify(token, UserConstants.JWT_KEY.getBytes());//验证令牌
            JWTValidator.of(token).validateDate(DateUtil.date()); //验证是否过期

            //读取信息
            final JWT jwt = JWTUtil.parseToken(token);
            UserInfo userInfo = BeanUtil.toBean(jwt.getPayloads(), UserInfo.class);
            return userService.load(userInfo.getUserId());
        } catch (Exception e) {
            return null;
        }
    }
}
